/** Communication protocoll
 *  @author ChrM and Sun Microsystems */

package com.jtalker.server;

import com.jtalker.server.JTalkerMember.Message;
import com.jtalker.server.JTalkerParser.OneMember;

/**
 * A kind of protocol that manipulate the message between server and client. For instance XML message protocol.
 * 
 * @author Administrator
 * 
 */
public class JTalkerProtocol {
	/** Constructor */
	public JTalkerProtocol(JTalkerServerThread parent) {
		this.parentThread = parent;
		this.myParser = new JTalkerParser();
	}

	/** Gets the logger */
	public Logger getLogger() {
		return this.parentThread.getLogger();
	}

	/** Protocol main Loop */
	public String processInput(String input) {
		String theOutput = null;
		// boolean sendUpdateIndication = true;
		this.closeConnectionNow = false;
		if (state == WAITING) {
			// New Client connected -> Send greetings
			theOutput = "<JTALKER>";
			theOutput += "<" + JTalkerParser.MESSAGE + " content=\"Welcome on JTalker-Server " + JTalkerServer.RELEASE
					+ "\" type=\"info\"/>";
			theOutput += "</JTALKER>";
			state = LOGGING;
		} else if (state == LOGGING) {
			// Waiting for user name -> parse -> send new User List
			if (!this.myParser.parsing(input)) {
				theOutput = getProcessString(0);
				state = LOGGING;
			} else {
				// Is this an account creation?
				if (this.myParser.isNewMember()) {
					// create new account
					this.getLogger().write("+ New Member creation procedure starts...");
					if (this.myParser.getAMember(0) == null
							|| !JTalkerServer.isAllowedUserName(this.myParser.getAMember(0).name)
							|| !this.parentThread.parent.addGlobalUser(this.myParser.getAMember(0).name,
									this.myParser.getAMember(0).passwd)) {
						theOutput = getProcessString(1);
						this.getLogger().writeError("Can't create Member: " + this.myParser.getAMember(0).name);
						state = LOGGING;
					} else {
						// theOutput =
						// "<JTALKER><error message=\"[INFO] Account successfull created. Welcome.\"/></JTALKER>";
						theOutput = getProcessString(2);
						state = LOGGING;
					}

					this.closeConnectionNow = true;
				} else {
					// Member Login
					JTalkerMember tempMember = null;
					OneMember tempOneMember = this.myParser.getAMember(0);
					if (tempOneMember != null) {
						tempMember = this.parentThread.parent.addMember(tempOneMember.name, tempOneMember.passwd,
								this.parentThread.socket, tempOneMember.type, tempOneMember.sid);
					}

					if (tempMember == null) {
						theOutput = getProcessString(3); // Wrong Name or unregistered user
						state = LOGGING;
					} else {
						theOutput = "<JTALKER>";
						// Send Session ID after web-client login
						if (tempMember.getUserType().equals(JTalkerMember.USER_TYPE_WEBCLIENT)) {
							theOutput += "<MESSAGE content=\"" + tempMember.getSessionID() + "\" type=\"sid\" />";
						}
						theOutput += this.parentThread.parent.getXMLMemberList(this.parentThread.getMember());
						theOutput += "</JTALKER>";
						state = GET_PACKAGE;
					}
				}
			}
		} else if (state == GET_PACKAGE) {
			// Getting some XML-Packages
			if (input.startsWith("<JTALKER>") || input.startsWith("<jtalker>")) {
				// Start Parsing here
				if (!this.myParser.parsing(input)) {
					theOutput = getProcessString(0);
					state = GET_PACKAGE;
				} else {
					if (this.myParser.getMemberStatus() != null) {
						// Change Status of current User
						// sendUpdateIndication = false;
						this.parentThread.getMember().setUserStatus(this.myParser.getMemberStatus());
					}
					// System.out.println("------state------" + state + "getRecs" + this.myParser.getRecs());
					if (this.myParser.getRecs() != null) {
						// Message was send -> Save this data to passed Member (by Recs)
						for (int i = 0; i < this.myParser.getRecsAsArray().length; i++) {
							JTalkerMember tmpMem = this.parentThread.parent
									.getMember(this.myParser.getRecsAsArray()[i]);

							if (tmpMem != null) {
								// DEBUG
								this.getLogger().write(
										"+ Message from: " + this.parentThread.getMember().getUserName() + " to: "
												+ tmpMem.getUserName() + " type: " + this.myParser.getMessageType());

								String tmpMessage = this.myParser.getMessage();

								// make this HTML compatible
								tmpMessage = toHTMLString(tmpMessage);

								if (!tmpMem.addMessage(tmpMessage, this.parentThread.getMember().getUserName(),
										this.myParser.getMessageType(), this.myParser.getMessageSubject(),
										this.myParser.getMessageID())) {

									// Can't forward message (bigger problem)!
									// TODO: Handle this much better (e.g. sending error message to the sender?)
									this.getLogger().writeError(
											"*** ERROR: Runtime - can't JTalkerMember.addMessage() from: "
													+ this.parentThread.getMember().getUserName() + " to: "
													+ tmpMem.getUserName());
								}

								// Redirect Chatroom Messages add welcome message for new chat-room users and call it again!
								if (tmpMem.isChatroom()) {
									this.parentThread.parent.redirectChatroomMessages();
								}
							} else {
								this.getLogger().writeError(
										"*** Message with wrong receiver recieved, from: "
												+ this.parentThread.getMember().getUserInfo());
								// Add error message for the sender
								this.parentThread.getMember().addMessage(
										"Unknown user: " + this.myParser.getRecsAsArray()[i], "@server", "error", null,
										null);
							}
						}
					}
					if (this.myParser.getCommand() != null) {
						// No message in this package, but a command was received
						String retValCommand = this.parentThread.parent.getPlugInLoader()
								.invokePlugInMethod(this.myParser.getCommand(), this.parentThread.getMember(),
										this.myParser.getCommandID());
						boolean sendError = false;
						if (retValCommand == null) {
							retValCommand = this.parentThread.parent.getPlugInLoader().getLastError();
							sendError = true;
						}

						// Add response message
						this.parentThread.getMember().addMessage(JTalkerProtocol.toHTMLString(retValCommand),
								"@server", "command_response", null, this.myParser.getCommandID());

						if (sendError) {
							// Add error message
							this.parentThread.getMember().addMessage(JTalkerProtocol.toHTMLString(retValCommand),
									"@server", "error", null, this.myParser.getCommandID());
						}
					}

					// Add update indication
					/*
					 * if(sendUpdateIndication) { this.parent.getMember().addMessage(
					 * this.toHTMLString("Please send me your sign of life!"), "@server", "update", null); }
					 */

					// Standard post data: Send user List and all stored
					// messages
					theOutput = "<JTALKER>";
					theOutput += this.parentThread.parent.getXMLMemberList(this.parentThread.getMember());
					// + Messages for this user!!!
					theOutput += getAllMessgesForUser();
					theOutput += "</JTALKER>";
					state = GET_PACKAGE;
				}
			} else {
				theOutput = getProcessString(0); // Wrong protocol
				state = GET_PACKAGE;
			}
		}
		return theOutput;
	}

	/** Generates a XML-formatted list with all messages for this user */
	private String getAllMessgesForUser() {
		String revVal = "";
		// Check if the user exists (or the session is closed)
		if (this.parentThread.getMember() == null) {
			this.closeConnectionNow = true;
			return getProcessString(4);
		}
		while (this.parentThread.getMember().numOfMsgs() > 0) {
			// List all messages
			Message tempMsg = this.parentThread.getMember().getMessage();
			if (tempMsg != null) {
				revVal += "<" + JTalkerParser.MESSAGE + " content=\"";
				revVal += (tempMsg.content + "\"");
				if (tempMsg.subject != null) {
					revVal += (" subject=\"" + tempMsg.subject + "\"");
				}
				revVal += (" sender=\"" + tempMsg.sender + "\" type=\"" + tempMsg.type + "\" id=\"" + tempMsg.ID + "\"/>");
			}
		}
		return revVal;
	}

	/** Generates an unique error/info/process message. */
	public static String getProcessString(int errno) {
		switch (errno) {
		case 0:
			return "<JTALKER><" + JTalkerParser.MESSAGE
					+ " content=\"[ERROR] Wrong Protocol! Try again!\" type=\"error\"/></JTALKER>";
		case 1:
			return "<JTALKER><"
					+ JTalkerParser.MESSAGE
					+ " content=\"[ERROR] Can't create account - user exists or the name is not wellformed (only letters and numbers are allowed). Sorry.\" type=\"error\"/></JTALKER>";
		case 2:
			return "<JTALKER><" + JTalkerParser.MESSAGE
					+ " content=\"[INFO] Account successfull created. Welcome.\" type=\"info\"/></JTALKER>";
		case 3:
			return "<JTALKER><"
					+ JTalkerParser.MESSAGE
					+ " content=\"[ERROR] Wrong name/passwd or unregistered user. Try again!\" type=\"error\"/></JTALKER>";
		case 4:
			return "<JTALKER><" + JTalkerParser.MESSAGE
					+ " content=\"[ERROR] Session Closed.\" type=\"error\"/></JTALKER>";
		case 5:
			return "<JTALKER><" + JTalkerParser.MESSAGE
					+ " content=\"[UPDATE] You have new messages.\" type=\"update\"/></JTALKER>";
		default:
			return "<JTALKER><" + JTalkerParser.MESSAGE
					+ " content=\"Unspecified Server Message.\" type=\"info\"/></JTALKER>";
		}
	}

	/**
	 * Returns the specified string converted to a format suitable for HTML. All signle-quote, double-quote,
	 * greater-than, less-than and ampersand characters are replaces with their corresponding HTML Character Entity
	 * code.
	 * 
	 * @param in
	 *            the String to convert
	 * @return the converted String
	 */
	public static String toHTMLString(String in) {
		StringBuffer out = new StringBuffer();
		for (int i = 0; in != null && i < in.length(); i++) {
			char c = in.charAt(i);
			// ASCI Entity
			if (c == '\'') {
				out.append("&#39;");
			} else if (c == '\"') {
				out.append("&#34;");
			} else if (c == '<') {
				out.append("&lt;");
			} else if (c == '>') {
				out.append("&gt;");
			} else if (c == '&') {
				out.append("&amp;");
			}

			// ISO 8859-1 (DE) Character Entities - incomplete, should be correct
			// handled by the client (char-set)!
			else if (c == '\u00e4') {
				out.append("&#228;");
			} else if (c == '\u00c4') {
				out.append("&#196;");
			} else if (c == '\u00d6') {
				out.append("&#214;");
			} else if (c == '\u00f6') {
				out.append("&#246;");
			} else if (c == '\u00fc') {
				out.append("&#252;");
			} else if (c == '\u00dc') {
				out.append("&#220;");
			} else if (c == '\u00df') {
				out.append("&#223;");
			}

			// New Line killer
			else if (c == '\n') {
				out.append("_");
			} else if (c == '\r') {
				out.append("_");
			} else {
				out.append(c);
			}
		}
		return out.toString();
	}

	// Private members
	private static final int WAITING = 0;
	private static final int LOGGING = 1;
	private static final int GET_PACKAGE = 2;
	// private static final int CREATING_ACCOUNT = 3;
	private int state = WAITING;
	private JTalkerServerThread parentThread;
	private JTalkerParser myParser;
	public boolean closeConnectionNow = false;

}
